package com.douyin.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.douyin.entity.RecommendationCache;
import com.douyin.entity.UserBehavior;
import com.douyin.entity.Videos;
import com.douyin.mapper.RecommendationCacheMapper;
import com.douyin.mapper.UserBehaviorMapper;
import com.douyin.mapper.VideosMapper;
import com.douyin.service.RecommendationService;
import com.douyin.service.VideosService;
import com.douyin.vo.VideoVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class RecommendationServiceImpl extends ServiceImpl<RecommendationCacheMapper, RecommendationCache>
        implements RecommendationService{

    @Resource
    private UserBehaviorMapper userBehaviorMapper;

    @Resource
    private VideosMapper videosMapper;

    @Resource
    private VideosService videosService;

    // 行为权重配置（正向行为得分）
    private static final Map<Integer, BigDecimal> BEHAVIOR_WEIGHT = new HashMap<>();
    static {
        BEHAVIOR_WEIGHT.put(1, new BigDecimal("0.5"));   // 观看
        BEHAVIOR_WEIGHT.put(2, new BigDecimal("2.0"));   // 点赞
        BEHAVIOR_WEIGHT.put(3, new BigDecimal("1.5"));   // 评论
        BEHAVIOR_WEIGHT.put(4, new BigDecimal("2.5"));   // 收藏
        BEHAVIOR_WEIGHT.put(5, new BigDecimal("3.0"));   // 分享
        BEHAVIOR_WEIGHT.put(6, new BigDecimal("3.5"));   // 关注作者
    }

    // 推荐缓存有效期（2小时）
    private static final int CACHE_EXPIRE_HOURS = 2;

    /**
     * 个性化推荐核心逻辑：基于用户行为计算推荐分数
     */
    @Override
    public IPage<VideoVO> getPersonalRecommend(Long userId, Integer pageNum, Integer pageSize) {
        // 1. 先查询缓存
        IPage<RecommendationCache> cachePage = queryCacheByUser(userId, pageNum, pageSize);
        if (!cachePage.getRecords().isEmpty()) {
            // 缓存命中：转换为VideoVO返回
            return convertCacheToVideoVO(cachePage);
        }

        // 2. 缓存未命中：计算推荐结果并更新缓存
        refreshRecommendationCache(userId);

        // 3. 再次查询缓存
        cachePage = queryCacheByUser(userId, pageNum, pageSize);
        return convertCacheToVideoVO(cachePage);
    }

    /**
     * 热门推荐：基于视频热度（播放量、点赞数等）
     */
    @Override
    public IPage<VideoVO> getHotRecommend(Integer pageNum, Integer pageSize) {
        Page<Videos> page = new Page<>(pageNum, pageSize);
        // 按播放量降序 + 点赞数降序，取最近30天的视频
        LambdaQueryWrapper<Videos> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Videos::getStatus, 1)
                .ge(Videos::getCreatedAt, LocalDateTime.now().minusDays(30))
                .orderByDesc(Videos::getPlayCount)
                .orderByDesc(Videos::getLikeCount);

        IPage<Videos> videoPage = videosMapper.selectPage(page, queryWrapper);
        return videoPage.convert(videosService::convertToVO);
    }

    /**
     * 刷新用户推荐缓存：计算推荐分数并保存到recommendation_cache表
     */
    @Transactional
    @Override
    public void refreshRecommendationCache(Long userId) {
        log.info("开始刷新用户[{}]的推荐缓存", userId);

        // 1. 查询用户近90天的有效行为
        List<UserBehavior> userBehaviors = userBehaviorMapper.selectRecentBehaviors(
                userId, LocalDateTime.now().minusDays(90));

        if (userBehaviors.isEmpty()) {
            log.info("用户[{}]无有效行为，使用热门推荐填充缓存", userId);
            fillCacheWithHotVideos(userId);
            return;
        }

        // 2. 计算用户行为偏好（视频ID -> 累计得分）
        Map<Long, BigDecimal> videoScoreMap = calculateVideoScores(userBehaviors);

        // 3. 基于用户偏好找到相似视频（这里简化处理：直接取高分视频+相似用户喜欢的视频）
        Set<Long> candidateVideoIds = getCandidateVideos(videoScoreMap, userId);

        // 4. 过滤已观看/无效视频
        Set<Long> watchedVideoIds = userBehaviors.stream()
                .map(UserBehavior::getVideoId)
                .collect(Collectors.toSet());
        candidateVideoIds.removeAll(watchedVideoIds);

        // 5. 计算候选视频的最终推荐分数
        Map<Long, BigDecimal> finalScores = calculateFinalScores(candidateVideoIds, videoScoreMap);

        // 6. 排序并保存到缓存表
        saveRecommendationsToCache(userId, finalScores);
        log.info("用户[{}]推荐缓存刷新完成，推荐数量:{}", userId, finalScores.size());
    }

    /**
     * 从缓存表查询用户推荐数据
     */
    private IPage<RecommendationCache> queryCacheByUser(Long userId, Integer pageNum, Integer pageSize) {
        Page<RecommendationCache> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<RecommendationCache> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(RecommendationCache::getUserId, userId)
                .gt(RecommendationCache::getExpiredAt, LocalDateTime.now())  // 只取未过期的
                .orderByAsc(RecommendationCache::getRank);  // 按排序号升序
        return baseMapper.selectPage(page, queryWrapper);
    }

    /**
     * 将缓存数据转换为VideoVO
     */
    private IPage<VideoVO> convertCacheToVideoVO(IPage<RecommendationCache> cachePage) {
        List<Long> videoIds = cachePage.getRecords().stream()
                .map(RecommendationCache::getVideoId)
                .collect(Collectors.toList());

        // 批量查询视频详情
        Map<Long, VideoVO> videoVOMap = videosService.queryVideoMapByIds(videoIds);

        // 构建结果页
        Page<VideoVO> resultPage = new Page<>(cachePage.getCurrent(), cachePage.getSize());
        resultPage.setTotal(cachePage.getTotal());
        resultPage.setRecords(cachePage.getRecords().stream()
                .map(cache -> videoVOMap.get(cache.getVideoId()))
                .filter(Objects::nonNull)
                .collect(Collectors.toList()));
        return resultPage;
    }

    /**
     * 计算用户行为对视频的累计得分
     */
    /**
     * 计算用户行为对视频的累计得分（优化N+1查询问题）
     */
    private Map<Long, BigDecimal> calculateVideoScores(List<UserBehavior> behaviors) {
        Map<Long, BigDecimal> scoreMap = new HashMap<>();
        if (behaviors.isEmpty()) {
            return scoreMap;
        }

        // 1. 提取所有涉及的视频ID（去重）
        Set<Long> videoIds = behaviors.stream()
                .map(UserBehavior::getVideoId)
                .collect(Collectors.toSet());

        // 2. 批量查询视频信息（仅查ID和时长，减少数据传输）
        Map<Long, Integer> videoDurationMap = videosMapper.selectBatchIds(videoIds).stream()
                .filter(video -> video.getDuration() != null && video.getDuration() > 0)
                .collect(Collectors.toMap(Videos::getId, Videos::getDuration));

        // 3. 循环计算得分（从map中获取时长，避免多次查询）
        for (UserBehavior behavior : behaviors) {
            BigDecimal weight = BEHAVIOR_WEIGHT.getOrDefault(behavior.getBehaviorType(), BigDecimal.ZERO);
            if (weight.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }

            // 观看行为额外增加时长权重（从批量查询的map中获取视频时长）
            if (behavior.getBehaviorType() == 1 && behavior.getDuration() != null) {
                Integer videoDuration = videoDurationMap.get(behavior.getVideoId());
                if (videoDuration != null) {
                    BigDecimal durationRatio = new BigDecimal(behavior.getDuration())
                            .divide(new BigDecimal(videoDuration), 2, BigDecimal.ROUND_HALF_UP);
                    weight = weight.multiply(durationRatio.add(BigDecimal.ONE));  // 基础分+时长比例分
                }
            }

            // 累加得分（新行为权重更高：按时间衰减）
            LocalDateTime behaviorTime = behavior.getCreatedAt();
            long hoursPassed = java.time.Duration.between(behaviorTime, LocalDateTime.now()).toHours();
            BigDecimal timeFactor = new BigDecimal(1).divide(
                    new BigDecimal(1 + hoursPassed / 24), 2, BigDecimal.ROUND_HALF_UP);
            BigDecimal finalScore = weight.multiply(timeFactor);

            scoreMap.put(behavior.getVideoId(),
                    scoreMap.getOrDefault(behavior.getVideoId(), BigDecimal.ZERO).add(finalScore));
        }
        return scoreMap;
    }

    /**
     * 获取候选推荐视频ID（基于用户喜欢的视频扩展）
     */
    private Set<Long> getCandidateVideos(Map<Long, BigDecimal> videoScoreMap, Long userId) {
        Set<Long> candidateIds = new HashSet<>();

        // 1. 加入用户高分视频（前20%）
        List<Map.Entry<Long, BigDecimal>> sortedEntries = new ArrayList<>(videoScoreMap.entrySet());
        sortedEntries.sort(Map.Entry.<Long, BigDecimal>comparingByValue().reversed());
        int topN = Math.max(1, sortedEntries.size() / 5);  // 取前20%
        for (int i = 0; i < topN && i < sortedEntries.size(); i++) {
            candidateIds.add(sortedEntries.get(i).getKey());
        }

        // 2. 加入喜欢相同视频的用户也喜欢的视频
        List<Long> highScoreVideoIds = sortedEntries.stream()
                .limit(topN)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
        List<Long> similarUserIds = userBehaviorMapper.selectUsersByVideosAndBehaviors(
                highScoreVideoIds, Arrays.asList(2, 4));  // 点赞和收藏的用户视为相似用户
        similarUserIds.remove(userId);  // 排除自己

        if (!similarUserIds.isEmpty()) {
            List<Long> similarUserLikedVideos = userBehaviorMapper.selectVideosByUsersAndBehaviors(
                    similarUserIds, Arrays.asList(2, 4), 100);  // 相似用户喜欢的视频
            candidateIds.addAll(similarUserLikedVideos);
        }

        return candidateIds;
    }

    /**
     * 计算候选视频的最终推荐分数
     */
    private Map<Long, BigDecimal> calculateFinalScores(Set<Long> candidateVideoIds, Map<Long, BigDecimal> userPreference) {
        Map<Long, BigDecimal> finalScores = new HashMap<>();
        for (Long videoId : candidateVideoIds) {
            // 1. 基础分：用户偏好匹配度（这里简化为相关视频的平均得分）
            BigDecimal baseScore = userPreference.values().stream()
                    .map(score -> score.multiply(new BigDecimal("0.3")))  // 30%权重
                    .reduce(BigDecimal.ZERO, BigDecimal::add)
                    .divide(new BigDecimal(Math.max(1, userPreference.size())), 2, BigDecimal.ROUND_HALF_UP);

            // 2. 视频热度分（播放量+点赞数标准化）
            Videos video = videosMapper.selectById(videoId);
            if (video == null || video.getStatus() != 1) {
                continue;  // 过滤无效视频
            }
            BigDecimal hotScore = calculateHotScore(video).multiply(new BigDecimal("0.2"));  // 20%权重

            finalScores.put(videoId, baseScore.add(hotScore));
        }
        return finalScores;
    }

    /**
     * 计算视频热度分
     */
    private BigDecimal calculateHotScore(Videos video) {
        // 播放量10万=1分，点赞数1万=1分，评论数1千=1分，分享数500=1分
        BigDecimal playScore = new BigDecimal(video.getPlayCount()).divide(new BigDecimal("100000"), 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal likeScore = new BigDecimal(video.getLikeCount()).divide(new BigDecimal("10000"), 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal commentScore = new BigDecimal(video.getCommentCount()).divide(new BigDecimal("1000"), 2, BigDecimal.ROUND_HALF_UP);
        BigDecimal shareScore = new BigDecimal(video.getShareCount()).divide(new BigDecimal("500"), 2, BigDecimal.ROUND_HALF_UP);
        return playScore.add(likeScore).add(commentScore).add(shareScore);
    }


    /**
     * 用热门视频填充新用户缓存
     */
    /**
     * 当用户无行为数据时，用热门视频填充推荐缓存
     */
    private void fillCacheWithHotVideos(Long userId) {
        // 1. 先删除用户已有的过期缓存（避免重复数据）
        LambdaQueryWrapper<RecommendationCache> deleteWrapper = new LambdaQueryWrapper<>();
        deleteWrapper.eq(RecommendationCache::getUserId, userId);
        baseMapper.delete(deleteWrapper);

        // 2. 查询热门视频（取前100条，覆盖大部分场景）
        IPage<VideoVO> hotVideoPage = getHotRecommend(1, 100);
        List<VideoVO> hotVideos = hotVideoPage.getRecords();
        if (hotVideos.isEmpty()) {
            log.warn("热门视频为空，无法填充用户[{}]的推荐缓存", userId);
            return;
        }

        // 3. 构建缓存数据并批量保存
        LocalDateTime expireTime = LocalDateTime.now().plusHours(CACHE_EXPIRE_HOURS);
        List<RecommendationCache> cacheList = new ArrayList<>();
        for (int i = 0; i < hotVideos.size(); i++) {
            VideoVO videoVO = hotVideos.get(i);
            RecommendationCache cache = new RecommendationCache()
                    .setUserId(userId)
                    .setVideoId(videoVO.getId())
                    .setRank(i + 1)  // 排名从1开始
                    .setScore(new BigDecimal(100 - i))  // 热门视频分数递减（100,99,...）
                    .setExpiredAt(expireTime);
            cacheList.add(cache);
        }

        // 批量插入缓存表
        this.saveBatch(cacheList);
        log.info("用户[{}]无行为，已用热门视频填充缓存，数量:{}", userId, cacheList.size());
    }
    /**
     * 将计算好的推荐结果保存到recommendation_cache表
     * @param userId 用户ID
     * @param finalScores 视频最终得分（videoId -> score）
     */
    private void saveRecommendationsToCache(Long userId, Map<Long, BigDecimal> finalScores) {
        if (finalScores.isEmpty()) {
            log.warn("用户[{}]无有效推荐结果，不保存缓存", userId);
            return;
        }

        // 1. 先删除用户已有的缓存（无论是否过期，确保数据最新）
        LambdaQueryWrapper<RecommendationCache> deleteWrapper = new LambdaQueryWrapper<>();
        deleteWrapper.eq(RecommendationCache::getUserId, userId);
        baseMapper.delete(deleteWrapper);

        // 2. 按得分降序排序，取前200条（避免缓存过多数据）
        List<Map.Entry<Long, BigDecimal>> sortedEntries = new ArrayList<>(finalScores.entrySet());
        sortedEntries.sort(Map.Entry.<Long, BigDecimal>comparingByValue().reversed());
        int limit = Math.min(200, sortedEntries.size());
        List<Map.Entry<Long, BigDecimal>> topEntries = sortedEntries.subList(0, limit);

        // 3. 构建缓存数据
        LocalDateTime expireTime = LocalDateTime.now().plusHours(CACHE_EXPIRE_HOURS);
        List<RecommendationCache> cacheList = new ArrayList<>();
        for (int i = 0; i < topEntries.size(); i++) {
            Map.Entry<Long, BigDecimal> entry = topEntries.get(i);
            RecommendationCache cache = new RecommendationCache()
                    .setUserId(userId)
                    .setVideoId(entry.getKey())
                    .setRank(i + 1)  // 排名从1开始（1为最推荐）
                    .setScore(entry.getValue())  // 保存计算出的最终得分
                    .setExpiredAt(expireTime);
            cacheList.add(cache);
        }
        // 4. 批量保存到数据库
        this.saveBatch(cacheList);
        log.info("用户[{}]推荐结果已保存到缓存，数量:{}", userId, cacheList.size());
    }
}